/*
 * Copyright 2016-2019 yoara
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package yhao.infra.web.filter;

import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import yhao.infra.common.ContextHolder;
import yhao.infra.web.YhaoInfraWebProperties;
import yhao.infra.web.common.session.HttpServletRequestWrapper;
import yhao.infra.web.common.session.SessionService;
import yhao.infra.web.constants.WebCommonConstants;

import jakarta.servlet.*;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;

/**
 * 统一session过滤器
 *
 * @author yoara
 */
public class WebSessionFilter extends HttpServlet implements Filter {
	private static final long serialVersionUID = -365105405910803550L;
	private static final String UN_NEED_SID = "no_sid";
	private static String sessionId = "sid";
	private static String cookieDomain = "";
	private static String cookiePath = "/";
	private static String sessionIdHeaderKey = "";
	private static boolean hasSessionIdHeaderKey;
	private static String sessionSourceHeaderKey = "";
	private static boolean hasSessionSourceHeaderKey;
	private static boolean sessionToCacheImmediately;
	private static List<String> passPathPrefix;
	private static List<String> passPathPostfix;
	private static Map<String,Integer> sessionSourceMap = new HashMap<>();

	/**
	 * 增加来源数据信息
	 * @param sourceKey 配置session来源键
	 * @param expireTime 配置session有效期时间，单位为秒
	 */
	public static void addSessionSource(String sourceKey,Integer expireTime){
		sessionSourceMap.put(sourceKey,expireTime);
	}

	public static String getSessionId() {
		return sessionId;
	}

	public void doFilter(ServletRequest servletRequest,
			ServletResponse servletResponse, FilterChain filterChain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) servletRequest;
		HttpServletResponse response = (HttpServletResponse) servletResponse;
		String servletPath = request.getServletPath();

		if(checkPath(servletPath)){
			filterChain.doFilter(request, response);
			return;
		}
		//根据请求来源设置超时时间
		if(hasSessionSourceHeaderKey){
			makeSessionExpireTime(request);
		}
		String sid = null;
		if(hasSessionIdHeaderKey){
			sid = request.getHeader(sessionIdHeaderKey);
		}
		if(StringUtils.isEmpty(sid)){
			sid = getIdInCookie(request, sessionId);
		}
		//若sid不存在，则依照uuid规则新生成一条sid
		if (StringUtils.isEmpty(sid)) {
			sid = java.util.UUID.randomUUID().toString();
			if(StringUtils.isEmpty(request.getParameter(UN_NEED_SID))){
				setSidInCookie(response, sid);
			}
		}
		request.setAttribute(WebCommonConstants.REQUEST_SESSION_ID, sid);
		HttpServletRequestWrapper requestWrapper = new HttpServletRequestWrapper(sid, request,sessionToCacheImmediately);
		try{
			filterChain.doFilter(requestWrapper, servletResponse);
		}finally {
			//做session延时操作
			String nowSid = (String)requestWrapper.getAttribute(WebCommonConstants.REQUEST_SESSION_ID);
			if(nowSid!=null){
				SessionService.getInstance().expireSession(nowSid);
				if(!sessionToCacheImmediately){
					requestWrapper.saveSessionToCache();
				}
			}
		}
	}

	private boolean checkPath(String servletPath) {
		if(passPathPrefix !=null){
			for(String pu: passPathPrefix){
				if(servletPath.startsWith(pu)){
					return true;
				}
			}
		}
		if(passPathPostfix !=null){
			for(String pu: passPathPostfix){
				if(servletPath.endsWith(pu)){
					return true;
				}
			}
		}
		return false;
	}

	private void makeSessionExpireTime(HttpServletRequest request) {
		Integer expireTime = sessionSourceMap.get(request.getHeader(sessionSourceHeaderKey));
		if(expireTime!=null){
			ContextHolder.setContextData(SessionService.SessionExpireKey.class,expireTime);
		}else{
			ContextHolder.removeContextData(SessionService.SessionExpireKey.class);
		}
	}

	private String getIdInCookie(HttpServletRequest request, String key) {
		Cookie[] cookies = request.getCookies();
		if(cookies!=null && cookies.length>0){
			for(Cookie sCookie:cookies){
				if (sCookie.getName().equals(key)) {
					return sCookie.getValue();
				}
			}
		}
		return null;
	}

	private static void setSidInCookie(HttpServletResponse response, String sid) {
		Cookie sidCookies = new Cookie(sessionId, sid);
		sidCookies.setMaxAge(-1);
		if (StringUtils.isNotEmpty(cookieDomain)) {
			sidCookies.setDomain(cookieDomain);
		}
		sidCookies.setPath(cookiePath);
		sidCookies.setHttpOnly(true);
		response.addCookie(sidCookies);
	}

	public static void refreshSidInCookie(HttpServletRequest request,HttpServletResponse response) {
		String sid = UUID.randomUUID().toString();
		setSidInCookie(response, sid);
		request.setAttribute(WebCommonConstants.REQUEST_SESSION_ID, sid);
		((HttpServletRequestWrapper)request).refreshSid(sid);
	}

	public static void removeSidInCookie(HttpServletRequest request,HttpServletResponse response){
		setSidInCookie(response,null);
		request.setAttribute(WebCommonConstants.REQUEST_SESSION_ID, null);
		SessionService.getInstance().removeSession((String)request.getAttribute(WebCommonConstants.REQUEST_SESSION_ID));
	}

	public static void setSessionToCacheImmediately(HttpServletRequest request){
		request.setAttribute(WebCommonConstants.REQUEST_SESSION_TO_CACHE_IMMEDIATELY, true);
	}

	public void init(FilterConfig filterConfig){
		String sessionConfigStr = filterConfig.getInitParameter("sessionConfig");
		YhaoInfraWebProperties.Session sessionConfig =
				JSONObject.parseObject(sessionConfigStr,YhaoInfraWebProperties.Session.class);
		if (StringUtils.isNotBlank(sessionConfig.getCookieSidName())) {
			this.sessionId = sessionConfig.getCookieSidName();
		}
		if (StringUtils.isNotBlank(sessionConfig.getCookieDomain())) {
			this.cookieDomain = sessionConfig.getCookieDomain();
		}
		if (StringUtils.isNotBlank(sessionConfig.getCookiePath())) {
			this.cookiePath = sessionConfig.getCookiePath();
		}
		if (StringUtils.isNotBlank(sessionConfig.getSidHeaderKey())) {
			this.sessionIdHeaderKey = sessionConfig.getSidHeaderKey();
			this.hasSessionIdHeaderKey = true;
		}
		if (StringUtils.isNotBlank(sessionConfig.getSessionSourceHeaderKey())) {
			this.sessionSourceHeaderKey = sessionConfig.getSessionSourceHeaderKey();
			this.hasSessionSourceHeaderKey = true;
		}

		this.sessionToCacheImmediately = sessionConfig.isSessionToCacheImmediately();

		if (StringUtils.isNotBlank(sessionConfig.getPassPathPrefix())) {
			passPathPrefix = new ArrayList<>();
			for(String pua:sessionConfig.getPassPathPrefix().split(",")){
				if(StringUtils.isNotBlank(pua)){
					passPathPrefix.add(pua.trim());
				}
			}
		}
		if (StringUtils.isNotBlank(sessionConfig.getPassPathPostfix())) {
			passPathPostfix = new ArrayList<>();
			for(String pua:sessionConfig.getPassPathPostfix().split(",")){
				if(StringUtils.isNotBlank(pua)){
					passPathPostfix.add(pua.trim());
				}
			}
		}
	}
}
